package com.hzw.saas.web.storage.aspect;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.hzw.saas.api.group.IGroupResourceService;
import com.hzw.saas.api.storage.IResourceFileService;
import com.hzw.saas.api.storage.IResourceFolderService;
import com.hzw.saas.api.storage.bo.ResourceFileBO;
import com.hzw.saas.api.storage.bo.ResourceFolderStructBO;
import com.hzw.saas.api.storage.enums.ResourceAuthEnum;
import com.hzw.saas.common.security.utils.SecurityUtils;
import com.hzw.saas.common.util.SpringELUtils;
import com.hzw.saas.common.util.exception.ForbiddenException;
import com.hzw.saas.service.rbac.config.RbacConfig;
import com.hzw.saas.service.storage.annotation.FileAuthAfter;
import com.hzw.saas.service.storage.annotation.FileAuthBefore;
import com.hzw.saas.service.storage.annotation.FolderAuthAfter;
import com.hzw.saas.service.storage.annotation.FolderAuthBefore;
import com.hzw.saas.service.storage.util.StorageUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * 检查资源权限
 *
 * @author zzl
 * @since 03/07/2021
 */
@Aspect
@Component
@RequiredArgsConstructor
@Slf4j
public class ResourceAuthCheck {

    private final HttpStatus forbidden = HttpStatus.FORBIDDEN;

    private final RbacConfig rbacConfig;

    private final IResourceFileService fileService;
    private final IResourceFolderService folderService;
    private final IGroupResourceService groupResourceService;

    @Before("@annotation(fileAuthBefore)")
    @SuppressWarnings("unchecked")
    public void checkFileAuthBeforeInvoke(JoinPoint joinPoint, FileAuthBefore fileAuthBefore) {
        List<String> fileIdList = new ArrayList<>();
        if (StrUtil.isNotBlank(fileAuthBefore.fileId())) {
            String fileId = SpringELUtils.getSpelValue(joinPoint, fileAuthBefore.fileId(), String.class);
            if (StrUtil.isNotBlank(fileId)) {
                fileIdList.add(fileId);
            }
        }
        if (StrUtil.isNotBlank(fileAuthBefore.fileIds())) {
            List<String> fileIds = SpringELUtils.getSpelValue(joinPoint, fileAuthBefore.fileIds(), List.class);
            if (CollectionUtil.isNotEmpty(fileIds)) {
                for (String fileId : fileIds) {
                    if (StrUtil.isNotBlank(fileId)) {
                        fileIdList.add(fileId);
                    }
                }
            }
        }
        if (StrUtil.isNotBlank(fileAuthBefore.syncId())) {
            String syncId = SpringELUtils.getSpelValue(joinPoint, fileAuthBefore.syncId(), String.class);
            String fileId = StorageUtil.parseFileIdBySyncId(syncId);
            if (StrUtil.isNotBlank(fileId)) {
                fileIdList.add(fileId);
            }
        }
        this.checkFileAuth(fileIdList, fileAuthBefore.auth());
    }

    @AfterReturning(value = "@annotation(fileAuthAfter)", returning = "returnValue")
    public void checkFileAuthAfterInvoke(JoinPoint joinPoint, FileAuthAfter fileAuthAfter, ResponseEntity<ResourceFileBO> returnValue) {
        Optional<ResponseEntity<ResourceFileBO>> optional = Optional.ofNullable(returnValue);
        String fileId = optional.map(ResponseEntity::getBody).map(ResourceFileBO::getPid).orElse(null);
        this.checkFileAuth(ListUtil.list(true, fileId), fileAuthAfter.auth());
    }

    @Before("@annotation(folderAuthBefore)")
    @SuppressWarnings("unchecked")
    public void checkFolderAuthBeforeInvoke(JoinPoint joinPoint, FolderAuthBefore folderAuthBefore) {
        List<String> folderIdList = new ArrayList<>();
        if (StrUtil.isNotBlank(folderAuthBefore.folderId())) {
            String folderId = SpringELUtils.getSpelValue(joinPoint, folderAuthBefore.folderId(), String.class);
            if (StrUtil.isNotBlank(folderId) && !StorageUtil.isSpaceId(folderId)) {
                folderIdList.add(folderId);
            }
        }
        if (StrUtil.isNotBlank(folderAuthBefore.folderIds())) {
            List<String> folderIds = SpringELUtils.getSpelValue(joinPoint, folderAuthBefore.folderIds(), List.class);
            if (CollectionUtil.isNotEmpty(folderIds)) {
                for (String folderId : folderIds) {
                    if (StrUtil.isNotBlank(folderId) && !StorageUtil.isSpaceId(folderId)) {
                        folderIdList.add(folderId);
                    }
                }
            }
        }
        this.checkFolderAuth(folderIdList, folderAuthBefore.auth());
    }

    @AfterReturning(value = "@annotation(folderAuthAfter)", returning = "returnValue")
    public void checkFolderOwnAuthAfterInvoke(JoinPoint joinPoint, FolderAuthAfter folderAuthAfter, ResponseEntity<ResourceFolderStructBO> returnValue) {
        Optional<ResponseEntity<ResourceFolderStructBO>> optional = Optional.ofNullable(returnValue);
        String folderId = optional.map(ResponseEntity::getBody).map(ResourceFolderStructBO::getPid).orElse(null);
        this.checkFolderAuth(ListUtil.list(true, folderId), folderAuthAfter.auth());
    }

    private void checkFolderAuth(List<String> folderIdList, ResourceAuthEnum[] resourceAuths) {
        for (ResourceAuthEnum resourceAuth : resourceAuths) {
            switch (resourceAuth) {
                case OWN:
                    this.checkFolderOwnAuth(folderIdList);
                    return;
                case READ:
                    this.checkFolderReadAuth(folderIdList);
                    return;
            }
        }
    }

    private void checkFileAuth(List<String> fileIdList, ResourceAuthEnum[] resourceAuths) {
        for (ResourceAuthEnum resourceAuth : resourceAuths) {
            switch (resourceAuth) {
                case OWN:
                    this.checkFileOwnAuth(fileIdList);
                    return;
                case READ:
                    this.checkFileReadAuth(fileIdList);
                    return;
            }
        }
    }

    private void checkFolderReadAuth(List<String> folderIdList) {
        // TODO: ZZL@03/17/2021
    }

    private void checkFolderOwnAuth(List<String> folderIdList) {
        String userId = SecurityUtils.getUser().getUserId();
        folderService.assertAuthOwn(userId, folderIdList);
    }

    private void checkFileReadAuth(List<String> fileIdList) {
        String userId = SecurityUtils.getUser().getUserId();
        if (Objects.equals(userId, rbacConfig.getSuperAdminId())) {
            return;
        }
        for (String fileId : fileIdList) {
            boolean authRead = groupResourceService.checkAuthRead(userId, fileId);
            Assert.isTrue(authRead, () -> new ForbiddenException("无此文件读取权限: " + fileId));
        }
    }

    private void checkFileOwnAuth(List<String> fileIdList) {
        String userId = SecurityUtils.getUser().getUserId();
        fileService.assertAuthOwn(userId, fileIdList);
    }
}
